﻿/*
 * Twipler - Twitter Web Client with features.
 *  
 *  Copyright (C) 2009, 2010. Ian Quigley
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details. 
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */


using System;
using System.Linq;
using System.Web.UI.WebControls;
using System.Reflection;
using System.Web.Compilation;
using System.Collections;
using Twipler.App;
using Twipler.Data.Access;
using Twipler.Data.Entity;
using Twipler.Data.Session;


namespace Web.Controls
{
    public partial class EntityEdit : System.Web.UI.UserControl
    {
        /*
         *      REFACTOR THIS MESS !!!
         * 
         */
        
        private int _entityId = 0;
        private bool _readonly = false;
        private Base _entity = null;


        public delegate void UpdateEvent();
        public event UpdateEvent Update;

        public bool Readonly
        {
            get { return _readonly; }
            set { _readonly = value; }
        }
        
        public Base Entity
        {
            get { return _entity; }
            set { _entity = value; }
        }

        public string Caption { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack && Visible)
                UpdateDisplay();
        }

        public void UpdateDisplay()
        {
            this.Visible = (_entity != null) && !((Entity.Id == 0) && (_entity.UserCanCreate() == false));

            if (!this.Visible)
                return;

            if (Readonly == false)
                Readonly = ((_entity != null) && !_entity.UserCanModify());


            Current.EntityEdit = _entity;
            
            PropertyInfo[] props = _entity.GetType().GetProperties();
            

            rptControl.DataSource = props.Where(p => !IsIgnored(p)).OrderBy(p => SortOrder(p));
            rptControl.DataBind();
        }

        protected void rptControl_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            if (e.Item.ItemType == ListItemType.Footer)
            {
                Button btnUpdate = (Button)e.Item.FindControl("btnUpdate");

                if (_entity.Id == 0)
                    btnUpdate.Text = "New";

                if (Readonly)
                {
                    btnUpdate.Text = "Read only";
                    btnUpdate.CssClass = "greybutton";
                    btnUpdate.Attributes.Add("onclick", "return false;");
                }            
            }

            if ((e.Item.ItemType != ListItemType.AlternatingItem) && (e.Item.ItemType != ListItemType.Item))
                return;
                    
            PropertyInfo pi = (PropertyInfo)e.Item.DataItem;
            if (pi == null)
                return;

            object val = GetPropertyValue(_entity, pi);

            TextBox txt = (TextBox)e.Item.FindControl("txtValue");
            DropDownList drp = (DropDownList)e.Item.FindControl("drpList");
            CheckBox chkValue = (CheckBox)e.Item.FindControl("chkValue");

            if (  (IsType(pi, typeof(System.Enum))
                  || pi.PropertyType.FullName.StartsWith("Twipler.Data.Entity", StringComparison.OrdinalIgnoreCase))
                && !IsReadonly(pi) 
                && !IsIgnored(pi)                   
                )
            {
                drp.Visible = true;

                if (IsType(pi, typeof(System.Enum)))
                {
                    foreach (string name in Enum.GetNames(pi.PropertyType))
                    {
                        ListItem item = new ListItem(name);
                        if (val != null) item.Selected = (val.ToString() == name);
                        drp.Items.Add(item);
                    }
                }
                else if (IsType(pi, typeof(System.Boolean)))
                {
                    chkValue.Visible = true;
                    chkValue.Checked = (bool)val;                    
                }
                else
                {
                    IList baseList = GetList(pi);
                    if (baseList != null)
                    {
                        foreach (Base baseItem in baseList)
                        {
                            ListItem listItem = new ListItem(baseItem.ToString(), baseItem.Id.ToString());
                            if (val != null)
                                listItem.Selected = (baseItem.Id == ((Base)val).Id);

                            drp.Items.Add(listItem);
                        }
                    }
                }                
            }
            else if (IsType(pi, typeof(System.Boolean)))
            {
                chkValue.Visible = true;
                chkValue.Checked = (bool)val;
                chkValue.Enabled = !IsReadonly(pi);
            }

            else
            {
                txt.Visible = true;
                if (val != null)                
                    txt.Text = val.ToString();

                txt.ReadOnly = IsReadonly(pi);
                if (txt.ReadOnly)
                    txt.CssClass = "txtbox-ro";

                if (IsLargeText(pi))
                {
                    txt.TextMode = TextBoxMode.MultiLine;
                    txt.Rows = 10;
                    txt.Columns = 40;
                }

            }                        
        }

        protected void rptControl_ItemCommand(object source, RepeaterCommandEventArgs e)
        {
            if (e.CommandName != "Update")
                return;

            Literal litErrors = (Literal)e.Item.FindControl("litErrors");
            if (litErrors == null)
                return;

            
            litErrors.Text = "";

            _entity = Current.EntityEdit;

            if ((_entity.Id == 0) && _entity.UserCanCreate() == false)
            {
                litErrors.Text = "You can not have permission to create this data.";
                return;
            }


            if ((_entity.Id != 0) && _entity.UserCanModify() == false)
            {
                litErrors.Text = "You can not have permission to modify this information.";
                return;
            }

            foreach (RepeaterItem item in rptControl.Items)
            {
                HiddenField hidPropname = (HiddenField)item.FindControl("hidPropname");
                TextBox txt = (TextBox)item.FindControl("txtValue");
                DropDownList drp = (DropDownList)item.FindControl("drpList");
                CheckBox chkVal = (CheckBox)item.FindControl("chkValue");

                PropertyInfo pi = _entity.GetType().GetProperty(hidPropname.Value);

                object val = null;

                if (IsReadonly(pi))
                {                   

                    if (val == null)
                        val = GetPropertyValue(_entity, pi);
                }
                else
                {
                    val = ValueFromControls(pi, txt, drp, chkVal);
                }

                if (IsValid(_entity, pi, val) == false)
                {
                    litErrors.Text = pi.Name + " is invalid";
                    return;
                }
               
                pi.SetValue(_entity, val, null); 
            }

            Factory.SaveOrUpdate(_entity);
            NHibernateSessionManager.Instance.GetSession().Flush();
            

            if (Update != null)
                Update();
        }

        /// <summary>
        /// Get an object value from Textbox or Dropdown etc based on property
        /// </summary>
        /// <param name="pi"></param>
        /// <param name="txtBox"></param>
        /// <param name="drpDown"></param>
        /// <returns></returns>
        private object ValueFromControls(PropertyInfo pi, TextBox txtBox, DropDownList drpDown, CheckBox chkValue)
        {
            if (IsType(pi, typeof(System.Enum)))
            {
                return Enum.Parse(pi.PropertyType, drpDown.SelectedItem.Value);
            }

            if (IsType(pi, typeof(System.Boolean)))
                return chkValue.Checked;

            if (IsType(pi, typeof(Base)))
            {
                if (drpDown.SelectedValue.Length == 0)
                    return null;

                return Factory.GetInstance(pi.PropertyType, int.Parse(drpDown.SelectedValue));                
            }

            if (IsType(pi, typeof(System.Int32)))
            {
                int i;
                if (int.TryParse(txtBox.Text, out i))
                    return i;
                
                return null;
            }

            return txtBox.Text;
        }


        private IList GetList(PropertyInfo pi)
        {
            EditSelectFrom[] selectFrom = (EditSelectFrom[])pi.GetCustomAttributes(typeof(EditSelectFrom), false);

            if (selectFrom.Length == 1)
            {
                MethodInfo mi = _entity.GetType().GetMethod(selectFrom[0].MethodName);
                if (mi != null)
                    return mi.Invoke(_entity, null) as IList;
            }

            return NHibernateSessionManager.Instance.GetSession().Find("from " + pi.PropertyType.Name);
        }

        /// <summary>
        /// Is the stirng input valid
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="pi"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        private bool IsValid(Base entity, PropertyInfo pi, object val)
        {
            EditStringType[] stringtypes = (EditStringType[])pi.GetCustomAttributes(typeof(EditStringType), false);
            if (stringtypes.Length == 0)
                return true;

            foreach (EditStringType validator in stringtypes)
            {
                if (validator.IsValid(entity, pi, val as string) == false)
                    return false;
            }

            return true;
        }


        /// <summary>
        /// Is the property readonly?
        /// </summary>
        /// <param name="pi"></param>
        /// <returns></returns>
        private bool IsReadonly(PropertyInfo pi)
        {
            if (Readonly)
                return true;

            if (pi.GetCustomAttributes(typeof(EditReadonly), false).Length > 0)
                return true;

            foreach (EditIf ifCondition in pi.GetCustomAttributes(typeof(EditIf),false))
                if (ifCondition.CanEdit(_entity, pi) == false)
                    return true;

            EditCondition[] conditions = (EditCondition[])pi.GetCustomAttributes(typeof(EditCondition), false);
            if (conditions.Count() == 0)
                return false;

            foreach (EditCondition condition in conditions)
                if ((condition.IsRoleBasedReadonly) && (condition.HasBeenMeet(_entity, pi) == false))
                    return true;

            return false;
        }

        /// <summary>
        /// Does this type derive from Type
        /// </summary>
        /// <param name="pi"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        private bool IsType(PropertyInfo pi, Type t)
        {
            return t.IsAssignableFrom(pi.PropertyType);
        }

        /// <summary>
        /// Ignore this property (don't show/update on edit)
        /// </summary>
        /// <param name="pi"></param>
        /// <returns></returns>
        private bool IsIgnored(PropertyInfo pi)
        {
            return pi.GetCustomAttributes(typeof(EditIgnore), true).Count() > 0;
        }

        /// <summary>
        /// Does this property contain EditStringType(LargeText)
        /// </summary>
        /// <param name="pi"></param>
        /// <returns></returns>
        private bool IsLargeText(PropertyInfo pi)
        {
            return pi.GetCustomAttributes(typeof(EditStringType), true).Any(p => ((EditStringType)p).StringType == EditStringType.Types.LargeText);
        }

        /// <summary>
        /// Get the EditOrder Order value for property
        /// </summary>
        /// <param name="pi"></param>
        /// <returns></returns>
        private int SortOrder(PropertyInfo pi)
        {
            EditOrder[] order = (EditOrder[])pi.GetCustomAttributes(typeof(EditOrder), true);
            if (order.Length == 0)
                return 0;

            return order[0].Order;
        }

        /// <summary>
        /// Get the property value from entity, or lookup EditDefault value.
        /// </summary>
        /// <param name="pi"></param>
        /// <returns></returns>
        private object GetPropertyValue(object entity, PropertyInfo pi)
        {
            // Get the value from the entity.
            if (entity != null)
            {
                object val = pi.GetValue(entity, null);
                if (val != null)
                    return val;
            }
            
            return null;

        }
    }
}